/*
 * Copyright 2012 Gregor Schauer
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package at.schauer.gregor.vienna.example.jpa;

import at.schauer.gregor.vienna.context.DefaultViennaContext;
import at.schauer.gregor.vienna.context.ViennaContext;
import at.schauer.gregor.vienna.convert.vienna.SpringAwareViennaConverterProvider;
import at.schauer.gregor.vienna.datastore.DataStore;
import at.schauer.gregor.vienna.datastore.database.HibernateDataStore;
import at.schauer.gregor.vienna.example.common.entity.Address;
import at.schauer.gregor.vienna.example.common.entity.Customer;
import at.schauer.gregor.vienna.example.common.entity.Person;
import at.schauer.gregor.vienna.example.common.service.Dao;
import at.schauer.gregor.vienna.metadata.JPAMetadataProvider;
import at.schauer.gregor.vienna.serializer.DefaultSerializer;
import at.schauer.gregor.vienna.serializer.decomposer.DirectFieldDecomposer;
import at.schauer.gregor.vienna.serializer.populator.DirectFieldPopulator;
import org.hibernate.SessionFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import org.springframework.orm.hibernate4.HibernateTransactionManager;
import org.springframework.orm.hibernate4.LocalSessionFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.sql.DataSource;
import java.io.IOException;
import java.util.LinkedHashSet;
import java.util.Properties;
import java.util.Set;
import java.util.UUID;

/**
 * This small example contains the steps required to persist simple JPA entities.
 * <p/>
 * <p/>
 * Note: Vienna does not implement JSR 317 (JPA 2.0)!<br/>
 * Thus only a limited set of the most common annotations is supported. This includes:
 * <ul>
 * <li>{@link javax.persistence.Column}</li>
 * <li>{@link javax.persistence.Entity}</li>
 * <li>{@link javax.persistence.GeneratedValue}</li>
 * <li>{@link javax.persistence.Id}</li>
 * <li>{@link javax.persistence.MappedSuperclass}</li>
 * <li>{@link javax.persistence.Transient}</li>
 * </ul>
 * Members annotated with {@link javax.persistence.Basic}, {@link javax.persistence.OneToOne},
 * {@link javax.persistence.OneToMany}, {@link javax.persistence.ManyToOne} and {@link javax.persistence.ManyToMany}
 * are also persisted but any applied options e.g. {@code javax.persistence.Basic#FetchType=LAZY} are ignored.
 *
 * @author Gregor Schauer
 */
public class JPAExample {
	private JPAExample() {
	}

	/**
	 * The following three steps are required for persisting the entities.<br>
	 * Unfortunately depending on the complexity of the use case this may be a lot of work.
	 * However, the initialization can be done by an IoC container like Spring to get rid of this code.
	 * An equivalent Spring configuration named <tt>jpa.demo.xml</tt> is distributed with this example.
	 * Instead of manually invoking the methods, the following two lines can be used.
	 * <code><pre>
	 * ApplicationContext context = new GenericXmlApplicationContext(new ClassPathResource("at/schauer/gregor/vienna/demo/jpa.demo.xml"));
	 * DataStore dataStore = context.getBean(DataStore.class);
	 * </pre></code>
	 */
	public static void main(String... args) {
		ApplicationContext applicationContext = new AnnotationConfigApplicationContext(Config.class);
		Dao service = applicationContext.getBean(Dao.class);

		/**
		 * Let's precede with the business logic.
		 */
		// The application specific log can be created. In this case it is a simple class named DAO which
		// directly uses the predefined data store.

		// Now the entities can be created... a customer who lives on the Broadway :-)
		Set<Address> addresses = new LinkedHashSet<Address>(1);
		Customer customer = new Customer(1L, "John Doe", addresses, UUID.randomUUID());
		addresses.add(new Address(1L, "Broadway 127", 10002, "New York City", customer));
		// do some additional work...
		service.save(customer);

		// Anyplace, anywhere, anytime...
		Customer loaded = service.getCustomer(customer.getId());
		if (!customer.equals(loaded)) {
			throw new RuntimeException();
		}
	}

	@Configuration
	@EnableTransactionManagement
	public static class Config {
		@Bean
		public Dao dao() throws IOException {
			return new Dao();
		}

		@Bean
		public SessionFactory sessionFactory() throws IOException {
			LocalSessionFactoryBean sessionFactoryBean = new LocalSessionFactoryBean();
			sessionFactoryBean.setPackagesToScan(Person.class.getPackage().getName());
			sessionFactoryBean.setDataSource(dataSource());
			sessionFactoryBean.setHibernateProperties(hibernateProperties());
			sessionFactoryBean.afterPropertiesSet();
			return sessionFactoryBean.getObject();
		}

		@Bean
		public Properties hibernateProperties() {
			Properties properties = new Properties();
			properties.setProperty("hibernate.hbm2ddl.auto", "create");
			properties.setProperty("javax.persistence.validation.mode", "none");
			return properties;
		}

		@Bean
		public DataSource dataSource() {
			EmbeddedDatabaseBuilder databaseBuilder = new EmbeddedDatabaseBuilder();
			databaseBuilder.setType(EmbeddedDatabaseType.HSQL);
			return databaseBuilder.build();
		}

		@Bean
		public DataStore<Person> dataStore() throws IOException {
			return new HibernateDataStore<Person>(sessionFactory());
		}

		@Bean
		public ViennaContext viennaContext() {
			DefaultViennaContext ctx = new DefaultViennaContext();
			ctx.getConverterProviderMap().put(Object.class, new SpringAwareViennaConverterProvider());
			ctx.getMetadataProviderMap().put(Object.class, new JPAMetadataProvider<Object>());
			ctx.getSerializerMap().put(Object.class, serializer());
			return ctx;
		}

		@Bean
		public DefaultSerializer<?> serializer() {
			DefaultSerializer<Object> serializer = new DefaultSerializer<Object>();
			serializer.getDecomposerMap().put(Object.class, new DirectFieldDecomposer<Object>());
			serializer.getPopulatorMap().put(Object.class, new DirectFieldPopulator<Object>());
			return serializer;
		}

		@Bean
		public PlatformTransactionManager transactionManager() throws IOException {
			return new HibernateTransactionManager(sessionFactory());
		}
	}
}
